
// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

#include <type_traits>

#include "data_basic.hpp"
#include "scheduler_tick.hpp"
#include "task_call.hpp"


namespace scheduler_basic {

#if defined(__CORTEX_M)

    // 32位单片机上用32 位变量存储bit 标志更有效率
    #define _DELAY_CALLBACK_USE_FIXED_32BIT_FLAG_TYPE 1

#endif

    // 关闭超时检查，可以节省运行时间和ROM 占用
    // #define _DELAY_CALLBACK_DISABLE_MAX_DURATION 1


    /**
     * @brief 简单的函数回调式定时任务管理器，默认只支持单次延时执行的任务，循环执行可通过函数返回值实现，或者手动重复添加定时任务。
     *
     * 回调函数返回值非0 则被看作该任务的下一次执行延时数值，直接用该值更新任务的倒计时。
     *
     * **支持在定时任务执行过程中添加新任务**，但不能在执行中主动删除任务，任务函数只能通过返回0 将自己删除。
     * 新添加的任务在当次tick 中不会更新倒计时。
     *
     * 当DelayCallback 正在操作列表时，禁止从外部修改列表，比如，**禁止从中断函数中增加或删除任务**，在OS 环境下，
     * 禁止其他异步任务直接操作任务列表。因为中断函数或异步任务会在随机位置打断当前操作，无法高效的随时随地检测列表的变化。
     *
     * DelayCallback 中不提供标志位用于检测是否正在操作列表，如果有必要从中断函数或异步任务中检测DelayCallback 的状态，
     * 可在运行DelayCallback 的主程序中额外设置标志位，每次增加、删除任务或调用tick 函数时将标志置位。
     *
     * 如果最大任务数量不超过16，DelayCallback 内部将使用uint16_t 存储任务标志，在8 位AVR 单片机上，比如mega328p，
     * 相比用uint32_t 可以省下差不多200 字节的flash。
     *
     * 最大10 个任务时，在AVR 单片机上，DelayCallback 本身占用的Flash 约1.1K 字节。
     *
     *
     * @tparam TimeSource    获取时间戳的源，不关心时间的单位，可以是微秒、毫秒或时钟周期数，参考ArduinoMsSource 的实现。
     *
     * @tparam MaxTaskCount  最大可容纳的任务数量，超过该值后，再添加任务不会有效果。
     *                       同一个函数指针或回调对象可以多次重复添加进任务列表，且被视为不同的任务
     */
    template <typename TimeSource, size_t MaxTaskCount>
    class DelayCallback3 {
       public:
        using TimeType = typename TimeSource::TimeType;

        using IndexType = data_basic::fast_index_t<uint8_t>;

        static constexpr TimeType MAX_VALUE_OF_TIME = static_cast<TimeType>(0) - 1;

        static constexpr IndexType INVALID_INDEX = MaxTaskCount;

       private:
        using TaskType = TaskBox<TimeType>;

#ifndef _DELAY_CALLBACK_USE_FIXED_32BIT_FLAG_TYPE
        using TaskFlagType = data_basic::enough_and_fast_bit_flag_t<MaxTaskCount>;
#else
        using TaskFlagType = uint32_t;
#endif

        TaskType _task_list[MaxTaskCount] = {0};

        TimeSource _time;

        IndexType _max_task_count_in_one_tick = 0;

        IndexType _current_task_count = 0;

#ifndef _DELAY_CALLBACK_DISABLE_MAX_DURATION

        IndexType _max_duration_of_one_tick = 0;

#endif
        // 标记任务是否刚被添加，用于支持定时任务中添加新任务
        TaskFlagType _new_task_flag_bit = 0;

        // 标记一个任务指向的是普通函数指针，还是任务对象
        // 对应位是0 表示函数指针，1 表示任务对象
        TaskFlagType _task_type_flag_bit = 0;

        constexpr static size_t _MAX_MAX_COUNT = sizeof(_new_task_flag_bit) * 8;
        static_assert(MaxTaskCount <= _MAX_MAX_COUNT);

        auto test_new_task_bit(IndexType index) {
            return _new_task_flag_bit & (1 << index);
        }

        void set_new_task_bit(IndexType index) {
            _new_task_flag_bit |= (1 << index);
        }

        void clr_new_task_bit(IndexType index) {
            _new_task_flag_bit &= ~(1 << index);
        }

        void clr_all_new_task_bit() {
            _new_task_flag_bit = 0;
        }

        auto test_task_type_bit(IndexType index) {
            return _task_type_flag_bit & (1 << index);
        }

        void set_task_type_bit(IndexType index) {
            _task_type_flag_bit |= (1 << index);
        }

        void clr_task_type_bit(IndexType index) {
            _task_type_flag_bit &= ~(1 << index);
        }

        void clr_all_task_type_bit() {
            _task_type_flag_bit = 0;
        }

        void call_callback(IndexType i) {
            auto &task = _task_list[i];
            TimeType d;
            if (test_task_type_bit(i)) {
                // 调用任务对象
                d = task.call_object();
            }
            else {
                // 调用函数指针
                d = task.call();
            }

            if (task.down_counter == MAX_VALUE_OF_TIME) {
                // 如果任务对象在回调中自己delete 了自己，
                // 析构就会在回调返回前执行，不能用回调的返回值覆盖掉析构标志
                return;
            }
            else {
                if (d == MAX_VALUE_OF_TIME) {
                    // MAX_VALUE_OF_TIME 仅用于标记任务对象析构，
                    // 只应该在任务对象析构时设置，不应该作为返回值
                    d = d - 1;
                }
                task.down_counter = d;
            }
        }

        /**
         * @brief 创建一个任务，延时delay_time 后执行
         *
         * @param fptr
         * @param delay_time delay_time 不能为0 或MAX_VALUE_OF_TIME
         *
         * @return IndexType 任务的索引。若任务添加失败，返回MaxTaskCount，或INVALID_INDEX
         */
        IndexType add_task_ptr(void *fptr, TimeType delay_time) {
            if (this->not_full()) {
                if (delay_time == MAX_VALUE_OF_TIME) {
                    // MAX_VALUE_OF_TIME 是个特殊值，不能使用
                    delay_time = MAX_VALUE_OF_TIME - 1;
                }

                // 添加第一个任务时，重置计时起始时间
                if (_current_task_count == 0) {
                    this->reset_last_tick_time();
                }

                // 在任务列表中查找一个空位
                IndexType index;
                for (index = 0; index < MaxTaskCount; ++index) {
                    if (_task_list[index].fptr == nullptr)
                        break;
                }

                ++_current_task_count;
                _task_list[index].down_counter = delay_time;
                _task_list[index].fptr = fptr;
                set_new_task_bit(index);

                return index;
            }
            else {
                return INVALID_INDEX;
            }
        }

       public:
#ifndef _DELAY_CALLBACK_DISABLE_MAX_DURATION

        DelayCallback3(IndexType max_task_count_in_tick = 0, IndexType max_duration_of_tick = 0) :
            _max_task_count_in_one_tick(max_task_count_in_tick), _max_duration_of_one_tick(max_duration_of_tick) {}

#else

        DelayCallback3(IndexType max_task_count_in_tick = 0) :
            _max_task_count_in_one_tick(max_task_count_in_tick) {}

#endif


        /**
         * @brief 更新任务倒计时，执行延时完成的任务。
         *
         * 调用函数瞬间的时刻被缓存下来，用于更新所有任务的倒计时，
         * 所以任务执行的时序都是同步的，tick 函数执行过程的时间变化不会被任务感知到。
         * 缺点是，如果tick 执行时间太长，从第一个任务执行到最后一个任务的过程中，系统实际时间已经发生较大变化，
         * 任务的执行的实时性将会恶化。
         *
         * 比如，在最后一个任务更新时间的瞬间，如果按实际时间计算，该任务的延时已经完成，但由于tick 函数的实现，
         * 该任务只能等到下一次tick 才能运行。
         *
         */
        void tick() {
            IndexType task_counter = 0;
            IndexType task_count_before_tick = _current_task_count;  // 任务进行中可能增加任务，导致总数变化，所以要先记录tick 执行之前的总任务数
            // tick interval 是两次tick 开始的时间差，即上次tick  的运行时间加上退出tick 后主程序的运行时间
            auto tick_interval = _time.diff();
            if (tick_interval == 0) {
                return;
            }

            _time.reset();
            clr_all_new_task_bit();

            for (uint_fast8_t i = 0; i < MaxTaskCount; ++i) {
                // 跳过本次tick 过程中新添加的任务
                // fptr == nullptr 的任务视为已被删除或空任务，所以也跳过
                if (_task_list[i].fptr == nullptr || test_new_task_bit(i)) {
                    continue;
                }

                if ((_task_list[i].down_counter == 0) || (_task_list[i].down_counter == MAX_VALUE_OF_TIME)) {
                    // 将任务删除向后推迟一个周期，如果前一周期任务返回0，down_counter == 0,
                    // 此时它的fptr != nullptr，将在此处被删除
                    // 另一种情况是，如果fptr != nullptr，但是down_counter == MAX_VALUE_OF_TIME
                    // 说明这是个已经析构的任务对象，将它删除
                    remove_task(i);
                    // --task_count_before_tick;
                    continue;
                }

                if (_task_list[i].down_counter <= tick_interval) {  // 执行并处理返回值

#ifndef _DELAY_CALLBACK_DISABLE_MAX_COUNT_AND_MAX_DURATION  // 完全删除超时控制
    #ifndef _DELAY_CALLBACK_DISABLE_MAX_DURATION            // 只删除最大tick 时间，保留单次tick 最大任务数

                    if ((_max_task_count_in_one_tick == 0 || task_counter < _max_task_count_in_one_tick)
                        && (_max_duration_of_one_tick == 0 || (_time.diff() < _max_duration_of_one_tick))) {
    #else
                    if ((_max_task_count_in_one_tick == 0 || task_counter < _max_task_count_in_one_tick)) {
    #endif

#else
                    if constexpr (true) {
#endif
                        call_callback(i);
                    }
                    else {
                        _task_list[i].down_counter = 1;  // 把本次没轮到的任务的倒计时设为足够小的数
                    }
                }
                else {
                    _task_list[i].down_counter -= tick_interval;
                }

                // 跳过空任务和新添加的任务后，遍历的任务数等于tick 前的总任务数时，结束遍历
                ++task_counter;
                if (task_counter == task_count_before_tick) {
                    break;
                }
            }
        }


        /**
         * @brief 将上一次tick 的时间设置为当前时间
         *
         */
        void reset_last_tick_time() {
            _time.reset();
        }


        bool not_full() const {
            return _current_task_count < MaxTaskCount;
        }

        /**
         * @brief 在tick 函数调用后一次性执行的最大任务数
         *
         * 一次tick 中执行太多耗时的任务会占用过多CPU 时间，还会导致下一次tick 被推迟，从而降低任务执行的实时性。
         * 值为0 则不限制，值大于0 表示最多只执行这个数值的任务，其他任务只更新倒计时，不执行。
         *
         * @param max_count
         */
        void set_max_task_counter_in_tick(IndexType max_count) {
            _max_task_count_in_one_tick = max_count;
        }

#ifndef _DELAY_CALLBACK_DISABLE_MAX_DURATION

        /**
         * @brief 一次tick 能执行任务的最长时间
         *
         * 若值不为0，则每次任务退出时在tick 函数中检查时间，若超时，后续的任务只更新倒计时，不执行。
         *
         * @param max_duration
         */
        void set_max_duration_of_tick(IndexType max_duration) {
            _max_duration_of_one_tick = max_duration;
        }

#endif


        /**
         * @brief 将指定的任务删除，可以阻止任务下次执行
         *
         * @param index  任务的索引，即添加任务时获得的返回值
         */
        void remove_task(IndexType index) {
            if ((index < MaxTaskCount) && (_task_list[index].fptr != nullptr)) {
                --_current_task_count;
                // 任务对象析构以后fptr 就变成了野指针，不能直接设置box_ptr
                // 必须先确定对象有没有析构
                if (test_task_type_bit(index) && (_task_list[index].down_counter != MAX_VALUE_OF_TIME)) {
                    // 如果任务对象还没析构，就设置指针，告诉它，它已经被删除
                    auto *tob = reinterpret_cast<TaskObjectBase<TimeType> *>(_task_list[index].fptr);
                    tob->box_ptr = nullptr;
                }
                _task_list[index].down_counter = 0;
                _task_list[index].fptr = nullptr;
            }
        }


        /**
         * @brief 重置一个正在列表中的任务的倒计时
         *
         * @param index
         * @param delay_time   若为0 则无动作, 若为MAX_VALUE_OF_TIME，自动将其减一
         */
        void reset_task(IndexType index, TimeType delay_time) {
            if (delay_time == 0)
                return;
            else if (delay_time == MAX_VALUE_OF_TIME)
                delay_time = MAX_VALUE_OF_TIME - 1;

            _task_list[index].down_counter = delay_time;
        }


        /**
         * @brief 创建一个任务，延时delay_time 后执行
         *
         * @param fptr
         * @param delay_time 若值为0，则立即执行一次，然后再根据返回值加入任务列表
         *                   若返回值还是0，任务不会被加入列表
         *                   若值为MAX_VALUE_OF_TIME，自动将其减一
         *
         * @return IndexType 任务的索引。若任务添加失败，返回MaxTaskCount,
         */
        IndexType add_task(TimeType (*fptr)(), TimeType delay_time) {
            if (delay_time == 0) {
                delay_time = fptr();
            }

            if (delay_time != 0) {
                IndexType i = add_task_ptr(reinterpret_cast<void *>(fptr), delay_time);
                if (i != INVALID_INDEX) {
                    // 将回调类型设为函数指针
                    clr_task_type_bit(i);
                }

                return i;
            }

            return INVALID_INDEX;
        }

        /**
         * @brief 将任务对象加入列表，延时delay_time 后执行
         *
         * 默认不允许任务对象重复添加进列表。在任务对象析构时，它会自动把自己从任务列表删除，
         * 但如果一个任务对象被重复添加，它析构以后就不一定会发生什么。
         *
         * 定义_DELAY_CALLBACK_ALLOW_TASK_OBJECT_DUPLICATION 允许任务对象重复添加。
         *
         * @param fptr
         * @param delay_time 若值为0，则立即执行一次，然后再根据返回值加入任务列表
         *                   若返回值还是0，任务不会被加入列表
         *                   若值为MAX_VALUE_OF_TIME，自动将其减一
         *
         * @return IndexType 任务的索引。若任务添加失败，返回MaxTaskCount,
         */
        IndexType add_task(TaskObjectBase<TimeType> *tptr, TimeType delay_time) {
#ifndef _DELAY_CALLBACK_ALLOW_TASK_OBJECT_DUPLICATION
            // 默认不允许任务对象重复添加
            if (tptr->is_in_list()) {
                return INVALID_INDEX;
            }
#endif
            if (delay_time == 0) {
                if (tptr->fptr != nullptr) {
                    delay_time = call_task_object<TimeType>(tptr);
                }
            }

            if (delay_time != 0) {
                IndexType i = add_task_ptr(reinterpret_cast<void *>(tptr), delay_time);
                if (i != INVALID_INDEX) {
                    // 将回调类型设为任务对象
                    set_task_type_bit(i);
                    // 给box_ptr 赋值
                    tptr->box_ptr = &_task_list[i];
                }

                return i;
            }

            return INVALID_INDEX;
        }


        IndexType task_count() const {
            return _current_task_count;
        }


        IndexType max_task_count() const {
            return static_cast<IndexType>(MaxTaskCount);
        }
    };

}  // namespace scheduler_basic